{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Прогнозирование числа просмотров объявлений"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Автор: Бокиев Наврас <br>\n",
    "Slack: @bnavras"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://preview.ibb.co/gKQNPH/photofacefun_com_1524417017.jpg\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Импортируем необходимые библиотеки:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from scipy.sparse import hstack\n",
    "from pymorphy2 import MorphAnalyzer\n",
    "from matplotlib import pyplot as plt\n",
    "from sklearn.grid_search import GridSearchCV\n",
    "from xgboost import XGBClassifier, XGBRegressor\n",
    "from scipy.stats import normaltest, skew, kurtosis\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from scikitplot.estimators import plot_learning_curve\n",
    "from sklearn.metrics import mean_absolute_error, make_scorer\n",
    "from sklearn.model_selection import train_test_split, cross_val_score\n",
    "from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer\n",
    "from sklearn.preprocessing import OneHotEncoder, LabelEncoder, StandardScaler\n",
    "from sklearn.linear_model import LinearRegression, LogisticRegression, LogisticRegressionCV, Lasso, Ridge\n",
    "\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Содержание"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. Описание набора данных и признаков\n",
    "2. Первичный анализ данных\n",
    "3. Первичный визуальный анализ данных\n",
    "4. Инсайты и закономерности\n",
    "5. Выбор метрики\n",
    "6. Выбор модели\n",
    "7. Предобработка данных\n",
    "8. Кросс-валидация и настройка гиперпараметров модели\n",
    "9. Создание новых признаков и описание этого процесса\n",
    "10. Построение кривых валидации и обучения\n",
    "11. Прогноз для отложенной выборке\n",
    "12. Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Задача: \n",
    "Задача проекта - предсказать количество просмотров объявления до конца следующего дня с момента подачи"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Процесс сбора данных: \n",
    "Данные собраны сайтом объявлений Avito. Всего в датасете 423772 объявлений, которые были публикованы с 26 по 28 декабря 2016 года. \n",
    "Датасет можно найти на [Boosters](https://boosters.pro/champ_4), либо скачать по [ссылке](https://yadi.sk/d/RY_fdJab3Ue6BK)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ценность задачи:  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Знание количества просмотров может позволить сервису:\n",
    "- исследовать рынок\n",
    "- узнать примерную нагрузку на сайт\n",
    "- оптимизировать сервис\n",
    "- разместить рекламу на наиболее просматриваемых объявлениях\n",
    "- выявить объявления, которые наберут небольшое количество просмотров (исследовать причины, предпринять меры) и т.д.\n",
    "\n",
    "\n",
    "То есть результат поможет сделать сервис лучше!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Признаки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **start_time** - Время подачи объявления (МСК)\n",
    "- **title** - Заголовок объявления\n",
    "- **price** - Цена\n",
    "- **item_id** - Идентификатор объявления\n",
    "- **owner_type** - Тип владельца объявления\n",
    "    - *Private* – частный пользователь\n",
    "    - *Company* – компания\n",
    "    - *Shop* – владелец магазина на Avito\n",
    "- **category** - Категория объявления (Транспорт, недвижимость и т.д.)\n",
    "- **subcategory** - Подкатегория объявления\n",
    "- **region** - Регион размещения объявления\n",
    "- **param1**, **param2**, **param3** - Параметры объявления:\n",
    "    \n",
    "    На примере подкатегории «Детская одежда и обувь»\n",
    "\n",
    "    - *param1* = Вид одежды = «Для мальчиков»\n",
    "    - *param2* = Предмет одежды = “Верхняя одежда»\n",
    "    - *param3* = Размер = «50-56 cм (0-2 мес)»\n",
    "    \n",
    "    \n",
    "#### Целевая переменная:\n",
    "\n",
    "\n",
    "`item_views` - количество просмотров объявления"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Считаем данные и посмотрим на них:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('data/task3/train.csv', delimiter=';', index_col=0)  # проверить влияние id, затем удалить\n",
    "data.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Первичный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Взаимодействия признаков и их влияние на количесто просмотров "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим основные статистические характеристики вещественных признаков:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[['price', 'item_views']].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что большинство объявлений не выходит за рамки ста просмотров, при этом максимальное число просмотров равно 4140. Большая разница также между верхней картилью цены и ее максимумом. Возможно, мы имеем дело с выбросами, но вернемся к этому вопросу на этапе фильтрации данных, а пока посмотрим на матрицу корреляций:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[['price', 'item_views']].corr()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Вывод**: переменные слабо связаны."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Среди объявлений есть с ценой, равной нулю. Это, как правило, либо вакансии, либо желание людей избавиться от каких-то вещей, домашних животных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[data.price == 0].head(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также посмотрим характеристики других признаков: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.describe(include=['object']) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных много категориальных признаков. На данном этапе уже можно сделать некоторые выводы о количестве уникальных  значений, моде каждого признака."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Распределение целевого признака"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10,5))\n",
    "plt.xlim(0, 400)\n",
    "plt.hist(data.item_views, bins=1000, alpha=0.5, color='red', label='true');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим на нормальность и скошенность"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "normaltest_result = normaltest(data.item_views)\n",
    "print(f\"{normaltest_result.pvalue}\") "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"Скошенность: {skew(data.item_views)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы не будем обучать линейные и метрические модели, поэтому оставим целевую переменную без преобразований"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Пропуски"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "null_count = data.isnull().sum()\n",
    "null_count[null_count != 0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим, пропуски есть только в параметрах. Эти признаки описывают объект объявления. Соответствующие формы могут быть при создании не заполнены, либо товар не описывается какими-либо измерениями. В пользу последнего предположения - возрастание количества пропусков с ростом количества параметров. Заполним пропуски произвольной строкой, непересекающейся с имеющимися признаками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.fillna('Не заполнено');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Выбросы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На этапе первичного анализа мы выяснили, что верхняя квантиль цены и количества просмотров не привышают 10000 и 60 соответственно. Но предельные значения этих признаков гораздо больше. На момент исследования на сайте Avito самым дорогим объявлением был земельный участок стоимостью 2 858 076 000 руб. В наших же данных:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.price.max()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на несколько предельных значений вещественных признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sorted(data.price, reverse=True)[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sorted(data.item_views, reverse=True)[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы не будем удалять выбросы, потому что модели, которые мы собираемся обучать, устойчивы к ним."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. Первичный визуальный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Визуализируем для наглядности матрицу корреляций:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(rc={\"figure.figsize\": (10, 5)})\n",
    "sns.heatmap(data[['price', 'item_views']].corr(), \n",
    "            cmap=\"YlGnBu\",\n",
    "            annot=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь давайте исследуем категориальные признаки и выясним, влияет ли каждый признак на количество просмотров. Мы будем вычислять медиану целевого признака для каждого значения."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Начнем с категорий:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "median_target_for_category = data.groupby(['category']).item_views.median()\n",
    "plt.xticks(rotation=90)\n",
    "plt.bar(height=median_target_for_category, x=median_target_for_category.index);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что разные категории набирают разное количество просмотров. Теперь давайте посмотрим на зависимость цены от целевого признака."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(15,5))\n",
    "plt.xlim(0, 10000000)\n",
    "plt.scatter(data.price, data.item_views);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чем дороже объект объявления, тем меньше оно набирает просмотров. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На следующей диаграмме можно увидеть лидеров по количеству объявлений: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(15,3))\n",
    "plt.xticks(rotation=90)\n",
    "sns.countplot(data=data, x='region', \n",
    "              order=data.region.value_counts().index);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Но по количеству просмотров лидероми являются совсем другие регионы:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(20,10))\n",
    "plt.xticks(rotation=90)\n",
    "median_target_for_region = data.groupby(['region']).item_views.median()\n",
    "plt.bar(height=median_target_for_region,\n",
    "        x=median_target_for_region.index);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разное количество просмотров мы можем обнаружить также для подкатегорий и типов собственника"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(20,10))\n",
    "plt.xticks(rotation=90)\n",
    "median_target_for_subcategory = data.groupby(['subcategory']).item_views.median()\n",
    "plt.bar(height=median_target_for_subcategory, x=median_target_for_subcategory.index);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(15,5))\n",
    "median_target_for_owner_type = data.groupby(['owner_type']).item_views.median()\n",
    "plt.bar(height=median_target_for_owner_type, \n",
    "        x=median_target_for_owner_type.index);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Медиана для каждого значения категориальных признаков разная. То есть каждая категория таваров имеет определенную спицифику, что влияет на количество просмотров. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. Инсайты и закономерности"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обобщим некоторые наблюдения:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Чем дороже объект объявления, тем меньше просмотров оно набирает \n",
    "- Лидеры по количеству объявлений(Москва, Санкт-Петербург) и по количеству просмотров(Чеченская республика, Дагестан) не совпадают.\n",
    "- Больше просмотров набирают объявления категории Животные, чем Личные вещи\n",
    "- В целом: среднее число просмотров для каждого значения категориального признака ярко отличаются\n",
    "\n",
    "К инсайтам и закономерностьям мы еще вернемся на этапе создания новый признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5. Выбор метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В процессе исследования я буду считать две метрики:\n",
    "- Среднеквадратическое отклонение логорифмов\n",
    "- Среднюю абсолютную ошибку\n",
    "\n",
    "Первую будем считать, чтобы ориентироваться на лидербор [соревнования](https://boosters.pro/champ_4).\n",
    "\n",
    "\n",
    "Вторая метрика более простая и очевидная, выражается в тех же измерениях, что и целевая переменная. Отвечает на главный вопрос, на сколько просмотров мы в среднем ошибаемся?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='https://preview.ibb.co/mVLdWx/Code_Cogs_Eqn.gif'>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rmsle(x, y):\n",
    "    return np.sqrt(np.mean((np.log(x+1)-np.log(y+1))**2))\n",
    "rmsle_scorer = make_scorer(rmse)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. Выбор модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Перед нами задача регрессии: cпрогнозировать количество просмотров. Обучать будем следуюшие модели:\n",
    "\n",
    "- RandomForestRegressor\n",
    "- XGBoostRegressor\n",
    "\n",
    "У нас множество категориальных признаков, кроме того данные модели не требуют масштабирования вещественных признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 7. Предобработка данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = data.drop(columns=['item_views'], axis=1)\n",
    "y = data.item_views"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Преобразуем дату в соответствующий тип данных:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.start_time = X.start_time.apply(pd.to_datetime)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Заменим строки на числа и применим one hot encoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def to_ohe(series):\n",
    "    label_encoder = LabelEncoder()\n",
    "    one_hot_encoder = OneHotEncoder()\n",
    "   \n",
    "    owner_type_train = label_encoder.fit_transform(series)\n",
    "    return one_hot_encoder.fit_transform(owner_type_train.reshape(-1, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "owner_type_ohe = to_ohe(X.owner_type)\n",
    "category_ohe = to_ohe(X.category)\n",
    "region_ohe = to_ohe(X.region)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param1_ohe = to_ohe(X.param1.astype(str))\n",
    "param2_ohe = to_ohe(X.param2.astype(str))\n",
    "param3_ohe = to_ohe(X.param3.astype(str))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Bag of words | title"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из заголовка удалим некоторые символы, а слова приведем к нормальной форме. Затем составим мешок из 10000 самых популярых слов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "morph_analyzer = MorphAnalyzer()\n",
    "def title_preprocess(title):\n",
    "    title.replace(';', '').replace(',', '')\n",
    "    return ' '.join([morph_analyzer.normal_forms(word)[0] \n",
    "                     for word in title.split()])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.title = X.title.apply(title_preprocess)\n",
    "count_vectorizer = CountVectorizer(max_features=10000)\n",
    "title = count_vectorizer.fit_transform(X.title)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = X.price.values.reshape(-1, 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объединим все признаки:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_full = hstack([category_ohe, \n",
    "                 owner_type_ohe, \n",
    "                 region_ohe, \n",
    "                 title,\n",
    "                 price.reshape(-1,1),\n",
    "                 param1_ohe,\n",
    "                 param2_ohe,\n",
    "                 param3_ohe\n",
    "                 ])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Разобьем данные на обучающую и отложенную части"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_holdout, y_train,  y_holdout = train_test_split(X_full.tocsr(), y, test_size=0.3, random_state=17)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 8. Кросс-валидация"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf = RandomForestRegressor()\n",
    "rf.fit(X_train, y_train);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgboost = XGBRegressor(n_estimators=5627, \n",
    "                      max_depth=10, \n",
    "                      subsample=0.9, \n",
    "                      colsample_bytree=0.8, \n",
    "                      reg_lambda=0, \n",
    "                      reg_alpha=2, \n",
    "                      learning_rate=0.05, \n",
    "                      seed=250)\n",
    "xgboost.fit(X_train, y_train);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predict = rf.predict(X_holdout)\n",
    "rmse(y_holdout, predict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predict = xgboost.predict(X_holdout)\n",
    "rmse(y_holdout, predict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)\n",
    "rf_scores = cross_val_score(estimator=rf,\n",
    "                         X=X_train_full,\n",
    "                         y=y_train,\n",
    "                         cv=skf,\n",
    "                         scoring=rmse_scorer);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_scores.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgb_scores = cross_val_score(estimator=xgboost,\n",
    "                         X=X_train_full,\n",
    "                         y=y_train,\n",
    "                         cv=skf,\n",
    "                         scoring=rmse_scorer);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgb_scores.mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 9. Создание новых признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можно предположить, что объявления с длинным заголовком труднее читать и понимать. Скорее всего они не набирать много просмотров. Давайте создадим этот признак и посмотрим на распределение признака:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "title_len = X.title.apply(len)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10,5))\n",
    "plt.hist(title_len_train, bins=10);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Действительно, есть некоторый оптимум(то есть не очень короткие и не слишком длинные заголовки), который набирает больше просмотров."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все объявления в датасете опубликованы в интервале с 26 по 28 декабря 2016 года. То есть в публикации все объявления находились разное время. Логично, что объявления, которые были опубликованы раньше, имели преимущество по времени, за счет чего они могли набрать больше просмотров. Найдем самую позднюю дату и вычислим разницу в секундах для каждого объявления."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "max_date = X.start_time.max()\n",
    "time = X.start_time.apply(lambda x: (max_date - x).total_seconds())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавим сгенерированные признаки, обучим модель и проверим качество прогноза:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_full = hstack([X_full,\n",
    "                 time.values.reshape(-1,1),\n",
    "                 title_len.values.reshape(-1,1)\n",
    "                 ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_holdout, y_train,  y_holdout = train_test_split(X_full.tocsr(), y, test_size=0.3, random_state=17)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgboost.fit(X_full, y_train)\n",
    "rf.fit(X_train, y_train);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgboost.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predict = xgboost.predict(X_holdout)\n",
    "rmse(y_holdout, predict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predict = rf.predict(X_holdout)\n",
    "rmse(y_holdout, predict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Прогноз немного улучшился"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 10. Построение кривых валидации и обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим кривые валидации и обучения для градиентного бустинга: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(xgboost, X_train_full, y_train, figsize=(10, 7), cv=10, scoring=rmse_scorer);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И для смешанного леса:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(rf, X_train_full, y_train, figsize=(10, 7), cv=10, scoring=rmse_scorer);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим увеличение данных с некоторого порога не приводит к значительному улучшению качества прогноза. Улучшить прогноз можно за счет новых признаков."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 11. Прогноз для отложенной выборке"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Протестируем лучшую модель на отложенной выборке"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "predict = rf.predict(X_holdout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rmse(y_holdout, predict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат на отложенной выборке сопоставим с результатами на кросс-валидации."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 12. Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы уже говорили о том, что знание количества просмотров может сделать сервис лучше за счет:\n",
    "- оптимизации сайта \n",
    "- размещении рекламы на страницах наиболее просматриваемых объявлений\n",
    "- устранения причин небольшоего количества просмотров \"проблемных\" объявлений\n",
    "- информации о рынке (куда он движется и в какую сторону развиваться сервису?)\n",
    "\n",
    "Построенную модель могут применять сотрудники Avito для предсказания количества просмотров объявления, и, следовательно, для решения задач, перечисленных выше.\n",
    "\n",
    "Совершенству нет пределов! Дальнейшие пути улучшения решения: \n",
    "- собрать дополнительные признаки об объявлениях\n",
    "- сгенерировать новые признаки на основе имеющихся\n",
    "- объединить несколько прогнозных моделей"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
